'''
Created on 07.08.2011

@author: osman
'''

import opf.config as config
import sqlite3
from contextlib import contextmanager

from opf.constants import ANY

if config.USE_WEAKREF:
    import weakref

@contextmanager
def session(container):
    yield
    container.commit()

_tspo_tab = {0:'t', 1:'s', 2:'p', 3:'o'}

def _prepGet(quadro, delim = " and "):
    query = []; args = []
    if not isinstance(quadro, tuple):
        quadro = (ANY, ANY, quadro, ANY)
    else:
        l = len(quadro)
        if l == 2:
            quadro = tuple([ANY] + list(quadro) + [ANY])
        elif l == 3:
            quadro = tuple([ANY] + list(quadro))
    for n, arg in enumerate(quadro):
        if arg is ANY: continue
        query.append(_tspo_tab[n] + "=?")
        args.append(id(arg))
    return delim.join(query), tuple(args)

if config.USE_WEAKREF:
    from collections import namedtuple
    class _c(namedtuple('_c', 'val')):
        __slots__ = ()
        def __call__(self): return self.val
    def _prepAdd(quadro, tab):
        for arg in quadro:
            v = id(arg)
            try:
                tab[v] = weakref.ref(arg)
            except Exception:
                tab[v] = _c(val = arg)
            yield v
else:
    def _prepAdd(quadro, tab):
        for arg in quadro:
            v = id(arg)
            tab[v] = arg
            yield v

if config.USE_WEAKREF:
    def _prepRet(tab):
        def _do(item):
            nonlocal tab
            rslt = tuple(tab[x]() for x in item)
            return rslt
        return _do
else:
    def _prepRet(tab):
        def _do(item):
            nonlocal tab
            rslt = tuple(tab[x] for x in item)
            return rslt
        return _do

class Container:
    __sql_init = '''
    create table tspo (
        t integer not null,
        s integer not null,
        p integer not null,
        o integer not null,
        primary key (t, s, p, o)
    );
    create index t_ind on tspo (t);
    create index s_ind on tspo (s);
    create index p_ind on tspo (p);
    create index o_ind on tspo (o);
    '''
    __sql_erase = '''
    drop index t_ind;
    drop index s_ind;
    drop index p_ind;
    drop index o_ind;
    drop table tspo;
    '''
    def __init__(self):
        self.__conn = sqlite3.connect(":memory:")
        self.__conn.create_function('opfct', 2, self._opfct)
        self.__cur = self.__conn.cursor()
        self.__typetab = None
        self._init()
    if config.USE_WEAKREF:
        def _init(self):
            self.__tab = {id(0): lambda: 0}
            self.__cur.executescript(self.__sql_init)
            self.__conn.commit()
    else:
        def _init(self):
            self.__tab = {id(0): 0}
            self.__cur.executescript(self.__sql_init)
            self.__conn.commit()
    def _opfct(self, param, role):
        try:
            if self.__typetab:
                rtype = self.__typetab.get(role)
                if rtype:
                    (param,) = _prepRet(self.__tab)((param,))
                    return isinstance(param, rtype)
            return True
        except Exception as e:
            import sys
            # TODO: remove print(e, file = sys.stderr)
            print(e, file = sys.stderr)
            return False
    def erase(self):
        self.__cur.executescript(self.__sql_erase)
        self._init()
    def commit(self):
        self.__conn.commit()
    def len(self):
        self.__cur.execute('select count(*) from tspo')
        return self.__cur.fetchone()[0]
    def add(self, quadro):
        q = list(_prepAdd(quadro, self.__tab))
        if len(q) == 3: q.insert(0, id(0))
        self.__cur.execute('insert or ignore into tspo values (?,?,?,?)',
                           tuple(q))
        return self.__cur.rowcount
    def __lshift__(self, quadro):
        self.add(quadro) 
        return self
    def count(self, item):
        query, item = _prepGet(item)
        self.__cur.execute('select count(*) from tspo where ' + query, item)
        return self.__cur.fetchone()[0]
    def select(self, resnames, fromnames, wherestats, typetab = None):
        self.__typetab = typetab
        try:
            self.__cur.execute('select ' + resnames + ' from ' + fromnames + 
                               ' where ' + wherestats)
            result = (_prepRet(self.__tab)(x) for x in self.__cur.fetchall())
        finally:
            self.__typetab = None
        return result
    def delete(self, fromnames, wherestats, typetab = None):
        # TODO: this function does not work!
        self.__typetab = typetab
        try:
            query = 'delete from ' + fromnames + ' where ' + wherestats
            self.__cur.execute(query)
            result = self.__cur.rowcount
        finally:
            self.__typetab = None
        return result
    def __contains__(self, item):
        return self.count(item) > 0
    def __getitem__(self, item):
        query, item = _prepGet(item)
        self.__cur.execute('select * from tspo where ' + query, item)
        return (_prepRet(self.__tab)(x) for x in self.__cur.fetchall())
    def __delitem__(self, item):
        query, item = _prepGet(item)
        self.__cur.execute('delete from tspo where ' + query, item)
    def __iter__(self):
        self.__cur.execute("select * from tspo")
        all = self.__cur.fetchall()
        return (_prepRet(self.__tab)(x) for x in all)
    def __repr__(self):
        return 'opf.container.Container<%d>: '%id(self) + repr(set(self))
    def __str__(self):
        return 'opf.container.Container<%d>: '%id(self) + str(set(self))

if __name__ == "__main__":
    # simple tests:
    c = Container()
    I = 'I'; love = 'love'; Python = 'Python'
    know = 'know'; has = 'has'; cat = 'cat'
    Vlad = 'Vlad'; Alex = 'Alex'; use = 'use'
    with session(c):
        (c <<
            (1, I, love, Python) <<
            (1, Vlad, love, Python) <<
            (Alex, know, 1) <<
            (I, know, Alex) <<
            (Alex, has, cat) <<
            (cat, know, Python))
    print(">>c:")
    for q in c: print("\t", q)
    print(">>know in c: ", know in c)
    print(">>use in c: ", use in c)
    print(">>(I, know) in c: ", (I, know) in c)
    print(">>(Alex, has, cat) in c: ", (Alex, has, cat) in c)
    print(">>c.count((ANY, ANY, love, Python)): ",
          c.count((ANY, ANY, love, Python)))
    print(">>c[ANY, ANY, love, Python]:")
    for x in c[ANY, ANY, love, Python]: print("\t", x)
    print(">>c[know]:")
    for x in c[know]: print("\t", x)
    del c[ANY, ANY, love, Python]
    print(">>c after del c[ANY, ANY, love, Python]:")
    for q in c: print("\t", q)
    del c[Alex, has]
    print(">>c after del c[Alex, has]:")
    for q in c: print("\t", q)
    print('repr(c):\n\t', repr(c))
    print('str(c):\n\t', str(c))
